/*
 * Copyright (c) 2023 AlgoIdeas
 *
 * HDF is dual licensed: you can use it either under the terms of
 * the GPL, or the BSD license, at your option.
 * See the LICENSE file in the root of this repository for complete details.
 */

#include "aht20-d.h"

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <linux/i2c-dev.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define AHT20_STARTUP_TIME          20*1000 // 上电启动时间
#define AHT20_CALIBRATION_TIME      40*1000 // 初始化（校准）时间
#define AHT20_MEASURE_TIME          75*1000 // 测量时间

#define AHT20_CMD_CALIBRATION       0xBE // 初始化（校准）命令
#define AHT20_CMD_CALIBRATION_ARG0  0x08
#define AHT20_CMD_CALIBRATION_ARG1  0x00

/**
 * 传感器在采集时需要时间,主机发出测量指令（0xAC）后，延时75毫秒以上再读取转换后的数据并判断返回的状态位是否正常。
 * 若状态比特位[Bit7]为0代表数据可正常读取，为1时传感器为忙状态，主机需要等待数据处理完成。
 **/
#define AHT20_CMD_TRIGGER           0xAC // 触发测量命令
#define AHT20_CMD_TRIGGER_ARG0      0x33
#define AHT20_CMD_TRIGGER_ARG1      0x00

// 用于在无需关闭和再次打开电源的情况下，重新启动传感器系统，软复位所需时间不超过20 毫秒
#define AHT20_CMD_RESET             0xBA // 软复位命令

#define AHT20_CMD_STATUS            0x71 // 获取状态命令

/**
 * STATUS 命令回复：
 * 1. 初始化后触发测量之前，STATUS 只回复 1B 状态值；
 * 2. 触发测量之后，STATUS 回复6B： 1B 状态值 + 2B 湿度 + 4b湿度 + 4b温度 + 2B 温度
 *      RH = Srh / 2^20 * 100%
 *      T  = St  / 2^20 * 200 - 50
 **/
#define AHT20_STATUS_BUSY_SHIFT         7       // bit[7] Busy indication
#define AHT20_STATUS_BUSY_MASK          (0x1<<AHT20_STATUS_BUSY_SHIFT)
#define AHT20_STATUS_BUSY(status)       ((status & AHT20_STATUS_BUSY_MASK) >> AHT20_STATUS_BUSY_SHIFT)

#define AHT20_STATUS_MODE_SHIFT         5       // bit[6:5] Mode Status
#define AHT20_STATUS_MODE_MASK          (0x3<<AHT20_STATUS_MODE_SHIFT)
#define AHT20_STATUS_MODE(status)       ((status & AHT20_STATUS_MODE_MASK) >> AHT20_STATUS_MODE_SHIFT)

                                        // bit[4] Reserved
#define AHT20_STATUS_CALI_SHIFT         3       // bit[3] CAL Enable
#define AHT20_STATUS_CALI_MASK          (0x1<<AHT20_STATUS_CALI_SHIFT)
#define AHT20_STATUS_CALI(status)       ((status & AHT20_STATUS_CALI_MASK) >> AHT20_STATUS_CALI_SHIFT)
                                        // bit[2:0] Reserved

#define AHT20_STATUS_RESPONSE_MAX       6
#define AHT20_RESOLUTION                (1<<20)  // 2^20
#define AHT20_MAX_RETRY 10


static int32_t aht20_read(int32_t fp, uint8_t* buffer, uint32_t buffLen)
{
    int32_t retval = read(fp, buffer, buffLen);
    if (retval != buffLen) {
        printf("read() failed, %0X!\n", retval);
        return retval;
    }

    return retval;
}

static int32_t aht20_write(int32_t fp, uint8_t* buffer, uint32_t buffLen)
{
    int32_t retval = write(fp, buffer, buffLen);
    if (retval != buffLen) {
        printf("write (%02X) failed, %0X!\n", buffer[0], retval);
        return retval;
    }

    return retval;
}

// 发送获取状态命令
static uint32_t aht20_status_command(int32_t fp)
{
    uint8_t statusCmd[] = { AHT20_CMD_STATUS };
    return aht20_write(fp, statusCmd, sizeof(statusCmd));
}

// 发送软复位命令
static int32_t aht20_reset_command(int32_t fp)
{
    uint8_t resetCmd[] = {AHT20_CMD_RESET};
    return aht20_write(fp, resetCmd, sizeof(resetCmd));
}

// 发送初始化校准命令
static int32_t aht20_calibrate_command(int32_t fp)
{
    uint8_t clibrateCmd[] = {AHT20_CMD_CALIBRATION, AHT20_CMD_CALIBRATION_ARG0, AHT20_CMD_CALIBRATION_ARG1};
    return aht20_write(fp, clibrateCmd, sizeof(clibrateCmd));
}

/*
 * aht20_open:
 *	Open a file pointer to the appropiate I2C buss and aht20 address
 *********************************************************************************
 */
int32_t aht20_open(int32_t i2c_address, uint8_t aht20_address)
{
    char filename[20];
    int fp;

    snprintf(filename, 19, "/dev/i2c-%d", i2c_address);
    fp = open(filename, O_RDWR);
    if (fp < 0) {
        return fp;
    }

    if (ioctl(fp, I2C_SLAVE, aht20_address) < 0) {
        close(fp);
        return -1;
    }

    return fp;
}

int32_t aht20_close(int32_t fp)
{
    return close(fp);
}

// 读取温湿度值之前， 首先要看状态字的校准使能位Bit[3]是否为 1(通过发送0x71可以获取一个字节的状态字)，
// 如果不为1，要发送0xBE命令(初始化)，此命令参数有两个字节， 第一个字节为0x08，第二个字节为0x00。
int32_t aht20_calibrate(int32_t fp)
{
    uint32_t retval = 0;
    uint8_t buffer[AHT20_STATUS_RESPONSE_MAX];
    memset(&buffer, 0x0, sizeof(buffer));

    retval = aht20_status_command(fp);
    if (retval == -1) {
        return retval;
    }

    retval = aht20_read(fp, buffer, sizeof(buffer));
    if (retval == -1) {
        return retval;
    }

    if (AHT20_STATUS_BUSY(buffer[0]) || !AHT20_STATUS_CALI(buffer[0])) {
        retval = aht20_reset_command(fp);
        if (retval == -1) {
            return retval;
        }

        usleep(AHT20_STARTUP_TIME);
        retval = aht20_calibrate_command(fp);
        usleep(AHT20_CALIBRATION_TIME);
        return retval;
    }

    return 0;
}

// 发送 触发测量 命令，开始测量
int32_t aht20_start_measure(int32_t fp)
{
    uint8_t triggerCmd[] = {AHT20_CMD_TRIGGER, AHT20_CMD_TRIGGER_ARG0, AHT20_CMD_TRIGGER_ARG1};
    return aht20_write(fp, triggerCmd, sizeof(triggerCmd));
}

// 接收测量结果，拼接转换为标准值
int32_t aht20_get_measure_result(int32_t fp, float* temp, float* humi)
{
    int32_t retval = 0, i = 0;
    if (temp == NULL || humi == NULL) {
        return -1;
    }

    uint8_t buffer[AHT20_STATUS_RESPONSE_MAX];
    memset(&buffer, 0x0, sizeof(buffer));
    retval = aht20_read(fp, buffer, sizeof(buffer));  // recv status command result
    if (retval == 0) {
        return 0;
    }

    for (i = 0; AHT20_STATUS_BUSY(buffer[0]) && i < AHT20_MAX_RETRY; i++) {
        usleep(AHT20_MEASURE_TIME);
        retval = aht20_read(fp, buffer, sizeof(buffer));  // recv status command result
        if (retval == 0) {
            continue;
        }
    }

    if (i >= AHT20_MAX_RETRY) {
        printf("AHT20 device always busy!\r\n");
        return -1;
    }

    uint32_t humiRaw = buffer[1];
    humiRaw = (humiRaw << 8) | buffer[2];
    humiRaw = (humiRaw << 4) | ((buffer[3] & 0xF0) >> 4);
    *humi = humiRaw / (float)AHT20_RESOLUTION * 100;

    uint32_t tempRaw = buffer[3] & 0x0F;
    tempRaw = (tempRaw << 8) | buffer[4];
    tempRaw = (tempRaw << 8) | buffer[5];
    *temp = tempRaw / (float)AHT20_RESOLUTION * 200 - 50;

    return 0;
}
